package org.refresh.core;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.refresh.core.expr.BinaryExpression;
import org.refresh.core.expr.ConstantIntegerExpression;
import org.refresh.core.expr.Expression;
import org.refresh.core.expr.SourceVariableSumExpression;
import org.refresh.core.expr.SourceVariableValueExpression;
import org.refresh.core.expr.TargetVariableValueExpression;
import org.refresh.core.expr.UnaryExpression;
import org.refresh.core.expr.VariableSumExpression;
import org.refresh.core.expr.VariableValueExpression;
import com.sun.corba.se.spi.legacy.connection.GetEndPointInfoAgainException;

import choco.Constraint;
import choco.ContradictionException;
import choco.Problem;
import choco.Solver;
import choco.Var;
import choco.integer.IntDomainVar;
import choco.integer.IntExp;
import choco.integer.IntVar;
import choco.integer.search.AssignVar;
import choco.integer.search.DecreasingDomain;
import choco.integer.search.IncreasingDomain;
import choco.integer.search.MinDomain;
import choco.integer.search.StaticVarOrder;
import choco.real.RealExp;
import choco.real.RealVar;

/*******************************************************************************
 * Copyright (c) 2005, 2006 Jules White. All rights reserved. This program and
 * the accompanying materials are made available under the terms of the Eclipse
 * Public License v1.0 which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors: Jules White - initial API and implementation
 ******************************************************************************/
public class RefreshMatrixCore extends AbstractRefreshChocoCore {

	public class UndefinedAttributeException extends RuntimeException {

		public UndefinedAttributeException(String arg0) {
			super(arg0);
			// TODO Auto-generated constructor stub
		}

	}

	public class UnsupportedOperationException extends RuntimeException {

		public UnsupportedOperationException(String arg0) {
			super(arg0);
		}

	}

	public class UnsupportedExpressionType extends RuntimeException {

		public UnsupportedExpressionType(String arg0) {
			super(arg0);
		}

	}

	// use the source item to lookup a table with mappings to targets
	private Map<Object, Map<Object, IntVar>> mappingTable_ = new HashMap<Object, Map<Object, IntVar>>();

	public void setSetsToMap(List srcs, List trgs) {
		problem_ = new Problem();

		sourceItems_ = srcs;
		targetItems_ = trgs;
		for (Object src : srcs) {
			Map targettable = new HashMap<Object, IntVar>(19);
			for (Object trg : trgs) {
				IntDomainVar mapvar = problem_.makeBoundIntVar(
						src + "->" + trg, 0, 1);
				targettable.put(trg, mapvar);
			}
			mappingTable_.put(src, targettable);
		}
	}

	public void setSetsToMap(Object[] srcs, Object[] trgs) {
		setSetsToMap(Arrays.asList(srcs), Arrays.asList(trgs));
	}

	public List getTargets(Object src) {
		Map<Object, IntVar> targettable = getTargetTable(src);
		List valid = new ArrayList();
		for (Object trg : targettable.keySet()) {
			IntVar v = targettable.get(trg);
			if (((IntDomainVar) v).getVal() == 1) {
				valid.add(trg);
			}
		}
		return valid;
	}

	public Map<Object, IntVar> getTargetTable(Object src) {
		return mappingTable_.get(src);
	}

	public void testScheduling() {
		int ctotal = 200;
		int ntotal = 5;
		Problem pb = new Problem();
		// data
		int n = ctotal; // number of tasks (include the three fake tasks)
		int capa = 20000;
		int[] heights_data = new int[ctotal];
		for (int i = 0; i < ctotal; i++) {
			heights_data[i] = i;
		}
		int[] durations_data = new int[ctotal];
		Arrays.fill(durations_data, 1);

		IntDomainVar[] starts = pb.makeBoundIntVarArray("start", n, 0,
				ntotal - 1);
		IntDomainVar[] ends = pb.makeBoundIntVarArray("end", n, 0, ntotal);

		IntDomainVar[] duration = new IntDomainVar[n];
		IntDomainVar[] height = new IntDomainVar[n];
		for (int i = 0; i < height.length; i++) {
			duration[i] = (IntDomainVar) pb
					.makeConstantIntVar(durations_data[i]);
			height[i] = pb.makeEnumIntVar("duration " + i, new int[] { 0,
					heights_data[i] });
		}

		IntDomainVar[] bool = pb.makeEnumIntVarArray("taskIn?", n, 0, 1);
		IntDomainVar obj = pb.makeBoundIntVar("obj", 0, n);
		// post the cumulative
		pb.post(pb.cumulative(starts, ends, duration, height, capa));

		// post the channeling to know if the task is scheduled or not
		for (int i = 0; i < n; i++) {
			pb.post(pb.boolChanneling(bool[i], height[i], heights_data[i]));
		}
		// state the objective function
		pb.post(pb.geq(pb.sum(bool), 200));
		// pb.solve();
		Solver s = pb.getSolver();
		s.attachGoal(new AssignVar(new StaticVarOrder(bool),
				new DecreasingDomain()));
		s.addGoal(new AssignVar(new MinDomain(pb, starts),
				new IncreasingDomain()));
		// s.setObjective(obj);
		s.setFirstSolution(true);
		// s.setDoMaximize(true);
		s.setRestart(false);
		s.generateSearchSolver(pb);

		s.launch();
		System.out.println("Objective : " + (obj.getVal() - 3));
		for (int i = 3; i < starts.length; i++) {
			if (height[i].getVal() != 0)
				System.out.println("[" + starts[i].getVal() + " - "
						+ (ends[i].getVal() - 1) + "]:" + height[i].getVal());
		}
	}

	public void nextMappingWithResourceValues(String restype, int[] values,
			int cvalues[]) {
		Problem pb = new Problem();
		// data

		int extra = 0;
		int capa = 0;
		for (int i = 0; i < values.length; i++) {
			capa = Math.max(capa, values[i]);
		}
		for (int i = 0; i < values.length; i++) {
			if (capa > values[i]) {
				extra++;
			}
		}

		int srccount = cvalues.length;
		int n = srccount + extra; // number of tasks (include the
		// three fake tasks)

		int[] heights_data = new int[n];// {2, 1, 4, 2, 3,
		// 1, 5, 6, 2, 1, 3,
		// 1, 1, 2};

		for (int i = 0; i < cvalues.length; i++) {
			heights_data[i] = cvalues[i];
		}
		int[] durations_data = new int[n];
		Arrays.fill(durations_data, 1);

		IntDomainVar[] starts = pb.makeBoundIntVarArray("start", n, 0,
				values.length - 1);
		IntDomainVar[] ends = pb.makeBoundIntVarArray("end", n, 0,
				values.length);

		IntDomainVar[] duration = new IntDomainVar[n];
		IntDomainVar[] height = new IntDomainVar[n];
		for (int i = 0; i < height.length; i++) {
			duration[i] = (IntDomainVar) pb
					.makeConstantIntVar(durations_data[i]);
			height[i] = pb.makeEnumIntVar("duration " + i, new int[] { -1,
					heights_data[i] });
		}

		IntDomainVar[] bool = pb.makeEnumIntVarArray("taskIn?", n, 0, 1);
		IntDomainVar obj = pb.makeBoundIntVar("obj", 0, n);

		// set the fake tasks to establish the profile capacity of the ressource
		try {
			int node = 0;
			for (int i = 0; i < values.length; i++) {
				if (capa > values[i]) {
					starts[srccount + i].setVal(i);
					ends[srccount + i].setVal(i + 1);
					heights_data[srccount + node] = capa - values[i];
					height[srccount + node] = pb.makeEnumIntVar("fake " + i,
							new int[] { 0, capa - values[i] });
					height[srccount + node].setVal(capa - values[i]);
					node++;
				}
			}
			// starts[0].setVal(1);
			// ends[0].setVal(2);
			// height[0].setVal(2);
			// starts[1].setVal(2);
			// ends[1].setVal(3);
			// height[1].setVal(1);
			// starts[2].setVal(3);
			// ends[2].setVal(4);
			// height[2].setVal(4);
		} catch (ContradictionException e) {
			System.out
					.println("error, no contradiction expected at this stage");
		}

		// post the cumulative
		pb.post(pb.cumulative(starts, ends, duration, height, capa));

		// post the channeling to know if the task is scheduled or not
		for (int i = 0; i < n; i++) {
			pb.post(pb.boolChanneling(bool[i], height[i], heights_data[i]));

		}

		// state the objective function
		pb.post(pb.eq(pb.sum(bool), cvalues.length));

		// maximize the number of tasks placed in this profile
		Solver s = pb.getSolver();
		s.attachGoal(new AssignVar(new StaticVarOrder(bool),
				new DecreasingDomain()));
		s.addGoal(new AssignVar(new MinDomain(pb, starts),
				new IncreasingDomain()));
		// s.setObjective(obj);
		s.setFirstSolution(true);
		// s.setDoMaximize(true);
		s.setRestart(false);
		s.generateSearchSolver(pb);

		s.launch();

		System.out.println("Objective : " + (obj.getVal() - 3));
		for (int i = 0; i < starts.length - extra; i++) {
			if (height[i].getVal() != -1)
				System.out.println("[" + starts[i].getVal() + " - "
						+ (ends[i].getVal() - 1) + "]:" + height[i].getVal());
		}
	}

	public void nextMappingWithResourceValues2(String restype, int[] values,
			int cvalues[]) {
		Problem pb = new Problem();
		IntDomainVar[] starts = pb.makeBoundIntVarArray("starts",
				cvalues.length, 0, values.length - 1);
		IntDomainVar[] ends = pb.makeBoundIntVarArray("ends", cvalues.length,
				1, values.length);
		IntDomainVar[] height = new IntDomainVar[cvalues.length];
		for (int i = 0; i < cvalues.length; i++) {
			height[i] = pb.makeEnumIntVar("height" + i, new int[] { -1,
					cvalues[i] });
		}
		pb.post(pb.cumulative(starts, ends, pb.makeEnumIntVarArray("duration",
				cvalues.length, 1, 1), height, 1000));

		// post the channeling to know if the task is scheduled or not
		IntDomainVar[] bool = pb.makeEnumIntVarArray("bool", cvalues.length, 0,
				1);
		for (int i = 0; i < cvalues.length; i++) {
			pb.post(pb.boolChanneling(bool[i], height[i], cvalues[i]));

		}

		// state the objective function
		pb.post(pb.eq(pb.sum(bool), cvalues.length));

		// maximize the number of tasks placed in this profile
		Solver s = pb.getSolver();
		s.attachGoal(new AssignVar(new StaticVarOrder(bool),
				new DecreasingDomain()));
		s.addGoal(new AssignVar(new MinDomain(pb, starts),
				new IncreasingDomain()));
		// s.setObjective(obj);
		s.setFirstSolution(true);
		// s.setDoMaximize(true);
		s.setRestart(false);
		s.generateSearchSolver(pb);

		s.launch();

		// System.out.println("Objective : " + (obj.getVal() - 3));
		for (int i = 0; i < starts.length; i++) {
			if (height[i].getVal() != -1)
				System.out.println("[" + starts[i].getVal() + " - "
						+ (ends[i].getVal() - 1) + "]:" + height[i].getVal());
		}
	}

	public void setValidTargets(Object src, List valid) {
		Map<Object, IntVar> targettable = getTargetTable(src);
		for (Object trg : targettable.keySet()) {
			if (!valid.contains(trg)) {
				problem_.post(problem_.eq(targettable.get(trg), 0));
			}
		}
	}

	public void setInValidTargets(Object src, List invalid) {
		Map<Object, IntVar> targettable = getTargetTable(src);
		for (Object trg : invalid) {
			problem_.post(problem_.eq(targettable.get(trg), 0));
		}
	}

	public void requireAllMappedExactlyOnce() {
		for (Object src : mappingTable_.keySet()) {
			Map<Object, IntVar> targettable = getTargetTable(src);
			IntVar[] vars = new IntVar[targettable.size()];
			int count = 0;
			for (Object trg : targettable.keySet()) {
				vars[count] = targettable.get(trg);
				count++;
			}
			problem_.post(problem_.eq(problem_.sum(vars), 1));
		}
	}

	public void addSourceMappedCardinalityConstraint(Object src, int min,
			int max) {
		Map<Object, IntVar> targettable = getTargetTable(src);
		IntVar[] vars = new IntVar[targettable.size()];
		int count = 0;
		for (Object trg : targettable.keySet()) {
			vars[count] = targettable.get(trg);
			count++;
		}
		problem_.post(problem_.geq(problem_.sum(vars), min));
		problem_.post(problem_.leq(problem_.sum(vars), max));
	}

	public void addTargetMappedSourcesCardinalityConstraint(Object target,
			int min, int max) {
		IntVar[] vars = new IntVar[mappingTable_.size()];

		int count = 0;
		for (Object src : mappingTable_.keySet()) {
			IntVar v = mappingTable_.get(src).get(target);
			vars[count] = v;
			count++;
		}
		problem_.post(problem_.leq(problem_.sum(vars), max));
		problem_.post(problem_.geq(problem_.sum(vars), min));
	}

	public void addTargetMappedSourcesConstraint(Object target, int exactmapped) {
		IntVar[] vars = new IntVar[mappingTable_.size()];

		int count = 0;
		for (Object src : mappingTable_.keySet()) {
			IntVar v = mappingTable_.get(src).get(target);
			vars[count] = v;
			count++;
		}
		problem_.post(problem_.eq(problem_.sum(vars), exactmapped));
	}

	public void addTargetIntResourceConstraint(Object target, Object var,
			int val) {

		IntVar[] dvars = new IntVar[mappingTable_.size()];
		int count = 0;
		getTargetVariableValues(target).put(var, val);
		for (Object src : mappingTable_.keySet()) {
			IntVar v = mappingTable_.get(src).get(target);
			Integer value = (Integer) (sourceVariableValuesTable_.get(src))
					.get(var);
			int d = (value != null) ? value.intValue() : 0;
			IntVar dvar = problem_.makeBoundIntVar("" + src + "->" + target
					+ "(demands_of)->" + var, 0, d);
			problem_.post(problem_.eq(dvar, problem_.mult(d, v)));
			dvars[count] = dvar;
			count++;
		}
		problem_.post(problem_.leq(problem_.sum(dvars), val));
	}

	
	public void addRequiresMappingConstraint(Object src, Object req) {
		Map<Object, IntVar> stable = getTargetTable(src);
		Map<Object, IntVar> ttable = getTargetTable(req);
		for (Object o : stable.keySet()) {
			IntVar sv = stable.get(o);
			IntVar tv = ttable.get(o);
			problem_.post(problem_.implies(problem_.eq(sv, 1), problem_.eq(tv,
					1)));
		}
	}

	public void addSelectMappingConstraint(Object src, List set, int min,
			int max) {
		Map<Object, IntVar> stable = getTargetTable(src);

		for (Object o : stable.keySet()) {
			IntVar[] rvars = new IntVar[set.size()];
			for (int i = 0; i < set.size(); i++) {
				Object r = set.get(i);
				Map<Object, IntVar> ttable = getTargetTable(r);
				rvars[i] = ttable.get(o);
			}
			IntVar sv = stable.get(o);

			problem_.post(problem_.implies(problem_.eq(sv, 1), problem_.geq(
					problem_.sum(rvars), min)));
			problem_.post(problem_.implies(problem_.eq(sv, 1), problem_.leq(
					problem_.sum(rvars), max)));
		}
	}

	public void addSelectMappingConstraint(Object src, List set, int exactcard) {
		Map<Object, IntVar> stable = getTargetTable(src);

		for (Object o : stable.keySet()) {
			IntVar[] rvars = new IntVar[set.size()];
			for (int i = 0; i < set.size(); i++) {
				Object r = set.get(i);
				Map<Object, IntVar> ttable = getTargetTable(r);
				rvars[i] = ttable.get(o);
			}
			IntVar sv = stable.get(o);
			Constraint con = problem_.implies(problem_.eq(sv, 1), problem_.eq(
					problem_.sum(rvars), exactcard));
			postConstraint(src, src+" allows between ["+exactcard+".."+exactcard+"] of "+set+" to be selected", con);
		}
	}

	public void addExcludesMappingConstraint(Object src, Object req) {
		Map<Object, IntVar> stable = getTargetTable(src);
		Map<Object, IntVar> ttable = getTargetTable(req);
		for (Object o : stable.keySet()) {
			IntVar sv = stable.get(o);
			IntVar tv = ttable.get(o);
			Constraint con = problem_.implies(problem_.eq(sv, 1), problem_.eq(tv,
					0));
			postConstraint(src, src+" excludes "+req+" from being selected",con);
		}
	}

	public void addSourceMappingConstraint(Object src,
			BinaryExpression expression) {
		Map<Object, IntVar> targettable = getTargetTable(src);
		for (Object o : targettable.keySet()) {
			IntVar mapvar = targettable.get(o);
			problem_
					.post(getSourceMappingConstraint(src, o, mapvar, expression));
		}
	}
	
	public void postConstraint(Object src, String desc, Constraint con){
		problem_.post(con);
	}

	public IntVar getOrCreatePresenceVariable(Object item) {
		IntDomainVar pvar = (IntDomainVar) variableTable_.get(PRESENCE_PREFIX
				+ item);
		if (pvar == null) {
			pvar = problem_.makeBoundIntVar(PRESENCE_PREFIX + item, 0, 1);

			IntVar[] vars = null;
			if (mappingTable_.get(item) != null) {
				Map<Object, IntVar> targettable = getTargetTable(item);
				vars = new IntVar[targettable.size()];
				int count = 0;
				for (Object key : targettable.keySet()) {
					vars[count] = targettable.get(key);
					count++;
				}
			} else {
				vars = new IntVar[mappingTable_.size()];
				int count = 0;
				for (Object key : mappingTable_.keySet()) {
					vars[count] = mappingTable_.get(key).get(item);
					count++;
				}
			}
			problem_.post(problem_.implies(problem_.gt(problem_.sum(vars), 0),
					problem_.eq(pvar, 1)));
			problem_.post(problem_.implies(problem_.eq(pvar, 1), problem_.gt(
					problem_.sum(vars), 0)));
			variableTable_.put(PRESENCE_PREFIX + item, pvar);
		}
		return pvar;
	}

}
